/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

#include <string.h>

#include <jni.h>
#include "scenic_jni_Renderer.h"

#include "common.h"
#include "Debug.h"
#include "PlainPolygonRenderer.h"
#include "AAPolygonRenderer.h"
#include "HQAAPolygonRenderer.h"


using namespace Scenic::Renderer;



JNIEXPORT void JNICALL Java_scenic_jni_Renderer_nativeEnterCriticalSection
  (JNIEnv *, jobject)
{
	renderingLock.enterCriticalSection();
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_nativeLeaveCriticalSection
  (JNIEnv *, jobject)
{
	renderingLock.leaveCriticalSection();
}

JNIEXPORT jint JNICALL Java_scenic_jni_Renderer_createContextForCanvas
  (JNIEnv *, jobject, jint canvasId)
{
	Synchronized sync(renderingLock);

	ENTER(Java_scenic_jni_Renderer_createContextForCanvas);

	Canvas *canvas = Canvas::get(canvasId);
	Context *context = new Context(canvas);

	return context->id;
}

JNIEXPORT jint JNICALL Java_scenic_jni_Renderer_createContextForImage
  (JNIEnv *, jobject, jint imageId)
{
	Synchronized sync(renderingLock);

	ENTER(Java_scenic_jni_Renderer_createContextForImage);

	Context *context = new Context(Image::get(imageId));

	return context->id;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_deleteContext
  (JNIEnv *, jobject, jint contextId)
{
	Synchronized sync(renderingLock);

	ENTER(Java_scenic_jni_Renderer_deleteContext);

	Context *context = getContext(contextId);

	if(context)
		delete context;	
}

JNIEXPORT jboolean JNICALL Java_scenic_jni_Renderer_beginScene
  (JNIEnv *env, jobject obj, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_beginScene)

	Context *context = getContext(contextId);

	return context->beginScene() ? JNI_TRUE : JNI_FALSE;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_endScene
  (JNIEnv *env, jobject obj, jint contextId)
{
	ENTER(java_scenic_jni_Renderer_endScene);

	Context *context = getContext(contextId);

	context->endScene();
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_beginImage
  (JNIEnv *env, jobject obj, jint contextId, jint imageId)
{
	ENTER(Java_scenic_jni_Renderer_beginImage)

	Context *context = getContext(contextId);
	Image *image = Image::get(imageId);
	ClipArea *clip = new ClipArea();

	clip->parent = context->clip;
	clip->x = 0;
	clip->y = 0;
	clip->width = image->width;
	clip->height = image->height;
	clip->offsetX = 0;
	clip->offsetY = 0;
	clip->type = SURFACE_TYPE_COLOR;
	clip->texture = image->getBlock(0, 0)->texture;
	clip->texture->addRef();
	clip->surface = clip->texture->getSurface(0);

	context->setPrimitives(0);
	context->clip = clip;
	context->setRenderTarget(clip->surface);
}
	
JNIEXPORT void JNICALL Java_scenic_jni_Renderer_endImage
  (JNIEnv *env, jobject obj, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_endImage)

	Context *context = getContext(contextId);
	ClipArea *clip;

	context->setPrimitives(0);

	clip = context->clip;
	context->clip = context->clip->parent;
	context->setRenderTarget(context->clip->surface);
	delete clip;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_present
  (JNIEnv *env, jobject obj, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_present);
	Context *context = getContext(contextId);

	context->device->present();
	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setPolygonAntialias
  (JNIEnv *env, jobject obj, jint contextId, jboolean value)
{
	ENTER(Java_scenic_jni_Renderer_setPolygonAntialias);

	Context *context = getContext(contextId);

	context->polygonAntialias = value ? true : false;
	context->filterKernel = NULL;
	
}

static int getId(JNIEnv *env, jobject obj)
{
	jclass clazz = env->GetObjectClass(obj);
	jfieldID idField = env->GetFieldID(clazz, "id", "I");

	return env->GetIntField(obj, idField);
}

static void setId(JNIEnv *env, jobject obj, int id)
{
	jclass clazz = env->GetObjectClass(obj);
	jfieldID idField = env->GetFieldID(clazz, "id", "I");

	env->SetIntField(obj, idField, id);
}

struct FilterKernelData
{
	JNIEnv *env;
	jobject obj;
	jmethodID method;
};

static double filterFunc(double x, double y, void *data)
{
	FilterKernelData *d = (FilterKernelData*)data;

	double a=  d->env->CallDoubleMethod(d->obj, d->method, x - 0.5, y - 0.5);
	return a;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setPolygonAntialiasingFilter
  (JNIEnv *env, jobject, jint contextId, jobject obj, jdouble width, jdouble height)
{
	ENTER(Java_scenic_jni_Renderer_setPolygonAntialiasFilter);

#ifdef DIRECTX
	Context *context = getContext(contextId);
	FilterKernel *kernel = FilterKernel::get(getId(env, obj));

	printf("Id: %d\n", getId(env, obj));

	if(!kernel)
	{
		FilterKernelData data;
		jclass clazz = env->GetObjectClass(obj);;

		data.env = env;
		data.obj = obj;
		data.method = env->GetMethodID(clazz, "getValue", "(DD)D");

		printf("Create kernel\n");
		kernel = new FilterKernel(filterFunc, &data);
		printf("Done\n");
		setId(env, obj, kernel->id);
		printf("new object id: %d\n", getId(env, obj));
	}

	context->polygonAntialias = false;
	context->filterKernel = kernel;
	context->filterWidth = width;
	context->filterHeight = height;
#endif
}


JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setTransform
  (JNIEnv *env, jobject obj, jint context, jdouble m00, jdouble m01, 
  jdouble m10, jdouble m11, jdouble dx, jdouble dy)
{
	ENTER(Java_scenic_jni_Renderer_setTransform);
	Affine2 t;

	t.m00 = m00;
	t.m01 = m01;
	t.m10 = m10;
	t.m11 = m11;
	t.dx = dx;
	t.dy = dy;

	getContext(context)->transform = t;
	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setColorSpace
  (JNIEnv *, jobject, jint contextId, jboolean isLinear)
{
	ENTER(Java_scenic_jni_Renderer_setColorSpace);

	Context *context = getContext(contextId);

	context->clip->linearColorSpace = isLinear ? true : false;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_color
  (JNIEnv *env, jobject obj, jint contextId, jfloat red, jfloat green, 
  jfloat blue, jfloat alpha)
{
	ENTER(Java_scenic_jni_Renderer_color);

	RGBAFloat *color = &getContext(contextId)->color;
	
	color->red = red;
	color->green = green;
	color->blue = blue;
	color->alpha = alpha;

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_clear
  (JNIEnv *env, jobject obj, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_clear);

	Context *context = getContext(contextId);
	ClipArea *clip = context->clip;
	SizeRect rect;

	rect.x = clip->x - clip->offsetX;
	rect.y = clip->y - clip->offsetY;
	rect.width = clip->width;
	rect.height = clip->height;

	context->device->clear(context->color, &rect);
}

void drawBlock(Context *context, ImageBlock *block,
			   double clipX, double clipY, double clipWidth, double clipHeight,
			   int options)
{
	VertexStore vs(VSF_TEX1, context, &context->transform);
	int diffuse = context->color.getRGBA32();
	double sx1, sy1, sx2, sy2;
	BlendFactor srcBlend = (options & USAGE_PREMULTIPLIED_ALPHA) ? BLEND_ONE : BLEND_SRC_ALPHA;

	sx1 = (clipX - block->x) / block->width;
	sy1 = (clipY - block->y) / block->height;
	sx2 = sx1 + clipWidth / block->width;
	sy2 = sy1 + clipHeight / block->height;

	if(sx1 > 1.0 || sy1 > 1.0 || sx2 < 0.0 || sy2 < 0.0)
		return;

	for(int y = 0; y < 2; y++)
	{
		for(int x = 0; x < 2; x++)
		{
			double x2 = x;
			double y2 = y;

			if(x2 < sx1)
				x2 = sx1;
			else if(x2 > sx2)
				x2 = sx2;
			if(y2 < sy1)
				y2 = sy1;
			else if(y2 > sy2)
				y2 = sy2;

			double lx = x2 * block->width + block->x - clipX;
			double ly = y2 * block->height + block->y - clipY;

			vs.addVertex(lx, ly, diffuse, x2, y2);
		}
	}

	vs.addTriangle(0, 1, 2);
	vs.addTriangle(3, 1, 2);

	context->device->setTexture(0, block->texture);
	context->device->setSamplerStates(0, FILTER_LINEAR, ADDRESS_CLAMP);
	context->device->setTextureStageModulation(0, 1);
	context->device->setBlendFunc(srcBlend, BLEND_ONE_MINUS_SRC_ALPHA);

	vs.draw();

	context->device->setTexture(0, NULL);
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_drawImage
  (JNIEnv *env, jobject obj, jint contextId, jint imageId,
  jdouble x, jdouble y, jdouble width, jdouble height)
{
	ENTER(Java_scenic_jni_Renderer_drawImage);

	Context *context = getContext(contextId);
	Image *image = Image::get(imageId);

	context->setPrimitives(NULL);

	if(!image)
	{
		return;
	}

	for(int row = 0; row < image->getNumRows(); row++)
	{
		for(int col = 0; col < image->getNumCols(); col++)
		{
			drawBlock(context, image->getBlock(row, col), x, y, width, height, image->options);
		}
	}
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_solidColor
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_solidColor);


	Context *context = getContext(contextId);
	VertexStore vs(VSF_TEX0, context, 0);
	int diffuse = context->color.getRGBA32();
	ClipArea *clip = context->clip;

	context->setPrimitives(NULL);

	vs.addVertex(clip->x, clip->y, diffuse);
	vs.addVertex(clip->x + clip->width, clip->y, diffuse);
	vs.addVertex(clip->x, clip->y + clip->height, diffuse);
	vs.addVertex(clip->x + clip->width, clip->y + clip->height, diffuse);

	vs.addTriangle(0, 1, 2);
	vs.addTriangle(3, 1, 2);

	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE_MINUS_SRC_ALPHA);

	vs.draw();
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_gradient
  (JNIEnv *, jobject, jint contextId, jint type, jint cycleMethod, jint imageId)
{
	ENTER(Java_scenic_jni_Renderer_gradient);

	Context *context = getContext(contextId);
	Image *image = Image::get(imageId);
	int diffuse = context->color.getRGBA32();
	VertexStore vs(VSF_TEX1, context, 0);
	ClipArea *clip = context->clip;
	Affine2 tm = context->transform;
	Affine2 itm = context->transform.inverse();
	Double2 sp, tp;
	
/*	if(tm.det() == 0.0 || fabs(itm.m00) > 1 || fabs(itm.m01) > 1 || 
		fabs(itm.m10) > 1 || fabs(itm.m11) > 1)
	{
		itm.m00 = 1;
		itm.m11 = 1;
		itm.m01 = 0.0;
		itm.m10 = 0.0;
		itm.dx = itm.dy = 0.0;
	} */

	context->setPrimitives(NULL);

	if(type == LINEAR_GRADIENT)
	{
		for(int y = 0; y < 2; y++)
		{
			for(int x = 0; x < 2; x++)
			{			
				sp.x = clip->x + clip->width * x;
				sp.y = clip->y + clip->height * y;
				tp = itm.transform(sp);
				vs.addVertex(sp.x, sp.y, diffuse, tp.x, 0.5);
			}
		}

		vs.addTriangle(0, 1, 2);
		vs.addTriangle(1, 3, 2);
	}
	else if(type == TILE_GRADIENT)
	{
		for(int y = 0; y < 2; y++)
		{
			for(int x = 0; x < 2; x++)
			{			
				sp.x = clip->x + clip->width * x;
				sp.y = clip->y + clip->height * y;
				tp = itm.transform(sp);
				vs.addVertex(sp.x, sp.y, diffuse, tp.x, tp.y);
			}
		}

		vs.addTriangle(0, 1, 2);
		vs.addTriangle(1, 3, 2);
	}
	else if(type == RADIAL_GRADIENT)
	{
		double maxr = 0.0;
		int i, n = 100;

		for(int y = 0; y < 2; y++)
		{
			for(int x = 0; x < 2; x++)
			{			
				sp.x = clip->x + clip->width * x;
				sp.y = clip->y + clip->height * y;
				tp = itm.transform(sp);
				maxr = max(maxr, sqrt(tp.x * tp.x + tp.y * tp.y));
			}
		}

		tp.x = tp.y = 0.0;
		sp = tm.transform(tp);
		vs.addVertex(sp.x, sp.y, diffuse, 0, 0.5);
		for(i = 0; i < n; i++)
		{
			double a = i * (2.0 * M_PI / n);

			tp.x = cos(a) * maxr;
			tp.y = sin(a) * maxr;
			sp = tm.transform(tp);
			vs.addVertex(sp.x, sp.y, diffuse, maxr, 0.5);
		}
		for(i = 0; i < n; i++)
			vs.addTriangle(0, i == 0 ? n : i, i + 1);
	}
	else if(type == SQUARE_GRADIENT)
	{
		double maxr = 0.0;
		int i, n = 4;

		for(int y = 0; y < 2; y++)
		{
			for(int x = 0; x < 2; x++)
			{			
				sp.x = clip->x + clip->width * x;
				sp.y = clip->y + clip->height * y;
				tp = itm.transform(sp);
				maxr = max(maxr, sqrt(tp.x * tp.x + tp.y * tp.y));
			}
		}
		maxr *= sqrt(2.0);
		tp.x = tp.y = 0.0;
		sp = tm.transform(tp);
		vs.addVertex(sp.x, sp.y, diffuse, 0, 0.5);
		for(i = 0; i < n; i++)
		{
			double a = i * (2.0 * M_PI / n) + M_PI / 4.0;

			tp.x = cos(a) * maxr;
			tp.y = sin(a) * maxr;
			sp = tm.transform(tp);
			vs.addVertex(sp.x, sp.y, diffuse, maxr / sqrt(2.0), 0.5);
		}
		for(i = 0; i < n; i++)
			vs.addTriangle(0, i == 0 ? n : i, i + 1);
	}
	else if(type == CONICAL_GRADIENT)
	{
		double maxr = 0.0;
		int i, n = 200;

		for(int y = 0; y < 2; y++)
		{
			for(int x = 0; x < 2; x++)
			{			
				sp.x = clip->x + clip->width * x;
				sp.y = clip->y + clip->height * y;
				tp = itm.transform(sp);
				maxr = max(maxr, sqrt(tp.x * tp.x + tp.y * tp.y));
			}
		}

		tp.x = tp.y = 0.0;
		Double2 center = tm.transform(tp);
		for(i = 0; i <= n; i++)
		{
			double a = i * (2.0 * M_PI / n) + M_PI / 4.0;

			tp.x = cos(a) * maxr;
			tp.y = sin(a) * maxr;
			sp = tm.transform(tp);
			vs.addVertex(center.x, center.y, diffuse, (i + 0.5) / (double)n, 0.5);
			vs.addVertex(sp.x, sp.y, diffuse, i / (double)n, 0.5);
		}
		for(i = 0; i < n; i++)
			vs.addTriangle(i * 2, i * 2 + 1, i * 2 + 3);
	}

	context->device->setTexture(0, image->getBlock(0, 0)->texture);
	context->device->setSamplerStates(0, FILTER_LINEAR, cycleMethod != 0 ? ADDRESS_WRAP : ADDRESS_CLAMP);
	context->device->setTextureStageModulation(0, 1);

/*	context->device->setTexture(1, clip->texture);
	context->device->setSamplerStates(1, FILTER_POINT, ADDRESS_CLAMP);
	context->device->setTextureStageModulation(1, clip->getModulation());
*/
	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE_MINUS_SRC_ALPHA);

	vs.draw();

	context->device->setTexture(0, NULL);
	context->device->setTexture(1, NULL);

//	clip->texture->release();
//	delete clip;

	
}


JNIEXPORT void JNICALL Java_scenic_jni_Renderer_beginText
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_beginText);

	Context *context = getContext(contextId);

	context->setPrimitives(NULL);
	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE_MINUS_SRC_ALPHA);

	context->glyphVs = new VertexStore(VSF_TEX1, context, 0);
}


JNIEXPORT void JNICALL Java_scenic_jni_Renderer_endText
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_endText);

	Context *context = getContext(contextId);

	context->glyphVs->draw();
	context->glyphVs->empty();

	delete context->glyphVs;

	context->device->setTexture(0, NULL);
}


JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setTextTexture
  (JNIEnv *, jobject, jint contextId, jint imageId)
{
	ENTER(Java_scenic_jni_Renderer_setTextTexture);

	Context *context = getContext(contextId);
	Image *image = Image::get(imageId);

	if(!image)
	{
		
		return;
	}
	context->device->setTexture(0, image->getBlock(0, 0)->texture);
	context->device->setSamplerStates(0, FILTER_POINT, ADDRESS_CLAMP);
	context->device->setTextureStageModulation(0, 1);

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_drawGlyph
  (JNIEnv *, jobject, jint contextId, jfloat tx, jfloat ty, jfloat tw, jfloat th, 
  jint sx, jint sy, jint sw, jint sh)
{
	ENTER(Java_scenic_jni_Renderer_drawGlyph);

	Context *context = getContext(contextId);

	VertexStore *vs = context->glyphVs;
	int color = context->color.getRGBA32();

	vs->addVertex(sx, sy, color, tx, ty);
	vs->addVertex(sx + sw, sy, color, tx + tw, ty);
	vs->addVertex(sx, sy + sh, color, tx, ty + th);
	vs->addVertex(sx + sw, sy + sh, color, tx + tw, ty + th);

	vs->addTriangle(0, 1, 2);
	vs->addTriangle(3, 1, 2);
	vs->endBlock();

	
}

//
// Polygon
//


JNIEXPORT void JNICALL Java_scenic_jni_Renderer_tessBegin
  (JNIEnv *env, jobject obj, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_tessBegin);

	Context *context = getContext(contextId);

#ifdef DIRECTX
	if(context->filterKernel)
		context->polygonRenderer = new HQAAPolygonRenderer(context);
	else 
#endif
	if(context->polygonAntialias)
		context->polygonRenderer = new AAPolygonRenderer(context);
	else
		context->polygonRenderer = new PlainPolygonRenderer(context);
	context->polygonRenderer->begin();

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_tessEnd
  (JNIEnv *env, jobject obj, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_tessEnd);

	Context *context = getContext(contextId);

	context->polygonRenderer->end();
	delete context->polygonRenderer;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_tessVertex
  (JNIEnv *env, jobject obj, jint contextId, jdouble x, jdouble y)
{
	ENTER(Java_scenic_jni_Renderer_tessVertex);

	Context *context = getContext(contextId);

	context->polygonRenderer->addVertex(x, y);
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_tessTriangle
  (JNIEnv *, jobject, jint contextId, jint vertex1, jboolean edge1, jint vertex2, jboolean edge2, 
  jint vertex3, jboolean edge3)
{
	ENTER(Java_scenic_jni_Renderer_tessTriangle);

	Context *context = getContext(contextId);

	context->polygonRenderer->addTriangle(vertex1, edge1 ? true : false, vertex2, 
		edge2 ? true : false, vertex3, edge3 ? true : false);
}

//
// Polyline
//

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_polylineSetStyle
  (JNIEnv *env, jobject, jint contextId, jfloat width, jint cap, jint join, 
  jfloat miterLimit, jfloatArray lineDashLengths, jdouble lineDashPhase)
{
	ENTER(Java_scenic_jni_Renderer_polylineSetStyle);

	Context *context = getContext(contextId);

	context->lineWidth = width;
	context->lineCap = cap;
	context->lineJoin = join;
	context->miterLimit = miterLimit;
	if(lineDashLengths)
	{
		jfloat *data = env->GetFloatArrayElements(lineDashLengths, NULL);
		jsize length = env->GetArrayLength(lineDashLengths);

		context->lineDashLengths.resize(length);
		for(int i = 0; i < length; i++)
			context->lineDashLengths[i] = data[i];
		env->ReleaseFloatArrayElements(lineDashLengths, data, JNI_ABORT);
	}
	else
	{
		context->lineDashLengths.resize(0);
	}
	context->lineDashPhase = lineDashPhase;

	
}

static LineRenderer lineRenderer;

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_polylineBegin
  (JNIEnv *, jobject, jint contextId, jboolean closed)
{
	ENTER(Java_scenic_jni_Renderer_polylineBegin);

	Context *context = getContext(contextId);

	context->lineRenderer = &lineRenderer;
	context->lineRenderer->begin(context, closed ? true : false);
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_polylineEnd
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_polylineEnd);

	Context *context = getContext(contextId);

	context->lineRenderer->end();
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_polylinePoint
  (JNIEnv *, jobject, jint contextId, jdouble x, jdouble y)
{
	ENTER(Java_scenic_jni_Renderer_polylinePoint);

	Context *context = getContext(contextId);
	Double2 p;

	p.x = x;
	p.y = y;

	context->lineRenderer->addPoint(p);
}

//
// Primitives
//

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_beginPrimitives
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_beginPrimitives);

	Context *context = getContext(contextId);

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setAttribute__III_3B
  (JNIEnv *env, jobject, jint contextId, jint index, jint size, jbyteArray data)
{
	ENTER(Java_scenic_jni_Renderer_setAttribute__III_3B);

	Context *context = getContext(contextId);
	ScenePrimitives *p = &context->scenePrimitives;
	jbyte *dataContent = env->GetByteArrayElements(data, NULL);
	int length = env->GetArrayLength(data);

	if(p->attributes[index].data)
		delete p->attributes[index].data;
	p->attributes[index].type = ATTRIBUTE_TYPE_CHAR;
	p->attributes[index].size = size;
	p->attributes[index].length = length / size;
	p->attributes[index].data = new char[length];
	memcpy(p->attributes[index].data, dataContent, length * sizeof(char));
	env->ReleaseByteArrayElements(data, dataContent, JNI_ABORT);

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setAttribute__III_3F
  (JNIEnv *env, jobject, jint contextId, jint index, jint size, jfloatArray data)
{
	ENTER(Java_scenic_jni_Renderer_setAttribute__III_3F);

	Context *context = getContext(contextId);
	ScenePrimitives *p = &context->scenePrimitives;
	jfloat *dataContent = env->GetFloatArrayElements(data, NULL);
	int length = env->GetArrayLength(data);

	if(p->attributes[index].data)
		delete p->attributes[index].data;
	p->attributes[index].type = ATTRIBUTE_TYPE_FLOAT;
	p->attributes[index].size = size;
	p->attributes[index].length = length / size;
	p->attributes[index].data = (char*)new float[length];
	memcpy(p->attributes[index].data, dataContent, length * sizeof(float));
	env->ReleaseFloatArrayElements(data, dataContent, JNI_ABORT);

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setFragmentParameter
  (JNIEnv *env, jobject, jint contextId, jint index, jfloatArray parameter)
{
	ENTER(Java_scenic_jni_Renderer_setFragmentParameter);

	Context *context = getContext(contextId);
	ScenePrimitives *p = &context->scenePrimitives;
	jfloat *parameterContent = env->GetFloatArrayElements(parameter, NULL);
	int length = env->GetArrayLength(parameter);
	int i;

	for(i = 0; i < length; i++)
		p->fragmentParameters[index][i] = parameterContent[i];
	for(i = length; i < 4; i++)
		p->fragmentParameters[index][i] = 0;
	env->ReleaseFloatArrayElements(parameter, parameterContent, JNI_ABORT);

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_setTexture
  (JNIEnv *env, jobject, jint contextId, jint index, jint imageId)
{
	ENTER(Java_scenic_jni_Renderer_setTexture);

	Context *context = getContext(contextId);
	Image *image = Image::get(imageId);
	ScenePrimitives *p = &context->scenePrimitives;

	p->textures[index] = image;

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_drawIndices
  (JNIEnv *env, jobject, jint contextId, jint type, jintArray indices)
{
	ENTER(Java_scenic_jni_Renderer_drawIndices);

	Context *context = getContext(contextId);
	ScenePrimitives *p = &context->scenePrimitives;
	jint *indicesContent = env->GetIntArrayElements(indices, NULL);
	int indicesLength = env->GetArrayLength(indices);
	Affine2 *tm = &context->transform;

	int fvf = 0;
	int length = p->attributes[0].length;
	int i;
	float *positions = (float*)p->attributes[POSITION_ATTRIBUTE].data;
	int *colors = (int*)p->attributes[COLOR_ATTRIBUTE].data;
	float *texCoords[1] = {0};

	if(p->attributes[TEXCOORD_ATTRIBUTE].data)
	{
		fvf = VSF_TEX1;
		texCoords[0] = (float*)p->attributes[TEXCOORD_ATTRIBUTE].data;
	}
	else
		fvf = VSF_TEX0;

	VertexStore vs(fvf, context, &context->transform);

	for(i = 0; i < length; i++)
	{
		if(texCoords[0])
			vs.addVertex(positions[0], positions[1], colors[0], texCoords[0][0], texCoords[0][1]);
		else
			vs.addVertex(positions[0], positions[1], colors[0]);
		positions += p->attributes[POSITION_ATTRIBUTE].size;
		colors += 1;
		if(texCoords[0])
			texCoords[0] += p->attributes[TEXCOORD_ATTRIBUTE].size;
	}

	if(p->textures[0])
	{
		context->device->setTexture(0, p->textures[0]->getBlock(0, 0)->texture);
		context->device->setSamplerStates(0, FILTER_LINEAR, ADDRESS_CLAMP);
		context->device->setTextureStageModulation(0, 1);
	}
	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE_MINUS_SRC_ALPHA);

	for(i = 0; i < indicesLength; i+=3)
	{
		vs.addTriangle(indicesContent[i], indicesContent[i + 1], indicesContent[i + 2]);
	}

	vs.endBlock();
	vs.draw();
	context->device->setTexture(0, 0);

	env->ReleaseIntArrayElements(indices, indicesContent, JNI_ABORT);

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_endPrimitives
  (JNIEnv *env, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_endPrimitives);

	Context *context = getContext(contextId);
	ScenePrimitives *p = &context->scenePrimitives;

	for(int i = 0; i < ATTRIBUTE_SIZE; i++)
	{
		if(p->attributes[i].data)
		{
			delete p->attributes[i].data;
			p->attributes[i].data = 0;
		}
		p->attributes[i].length = 0;
	}

	
}

//
// Surfaces
//

class TextureCache
{
	DynArray<Texture*> cache;
public:
	void addTexture(Texture *t);
	Texture *getTexture(int w, int h);
};

TextureCache textureCache;

void TextureCache::addTexture(Texture *t)
{
	t->addRef();
	cache.add(t);
}

Texture *TextureCache::getTexture(int w, int h)
{
	if(cache.size > 0)
	{
		Texture *t = cache[cache.size - 1];
	
		cache.size--;

		return t;
	}

	return NULL;
}

JNIEXPORT jint JNICALL Java_scenic_jni_Renderer_beginClip
  (JNIEnv *, jobject, jint contextId, jint x, jint y, jint width, jint height)
{
	ENTER(Java_scenic_jni_Renderer_beginClip);

	Context *context = getContext(contextId);
	ClipArea *clip = new ClipArea();

	context->setPrimitives(0);

	*clip = *context->clip;
	clip->parent = context->clip;
	clip->x = max(context->clip->x, x);
	clip->y = max(context->clip->y, y);
	clip->width = min(context->clip->x + context->clip->width, x + width) - clip->x;
	clip->height = min(context->clip->y + context->clip->height, y + height) - clip->y;
	clip->offsetX = 0;
	clip->offsetY = 0;
	clip->linearColorSpace = clip->parent->linearColorSpace;

	if(clip->width < 1 || clip->height < 1)
	{
		delete clip;
		
		return 0;
	}
	
	if(clip->texture)
		clip->texture->addRef();
	clip->surface->addRef();
	context->clip = clip;

	return 1;
}

JNIEXPORT jint JNICALL Java_scenic_jni_Renderer_beginSurface
  (JNIEnv *, jobject, jint contextId, jint x, jint y, jint width, jint height, jint type)
{
	ENTER(Java_scenic_jni_Renderer_beginSurface);

	Context *context = getContext(contextId);
	ClipArea *clip = new ClipArea();

	clip->parent = context->clip;
	clip->x = max(context->clip->x, x);
	clip->y = max(context->clip->y, y);
	clip->width = min(context->clip->x + context->clip->width, x + width) - clip->x;
	clip->height = min(context->clip->y + context->clip->height, y + height) - clip->y;
	clip->type = type;
	clip->texture = 0;
	clip->offsetX = clip->x;
	clip->offsetY = clip->y;
	clip->linearColorSpace = clip->parent->linearColorSpace;

	if(clip->width < 1 || clip->height < 1)
	{
		delete clip;
		
		return 0;
	}

	context->setPrimitives(0);
	context->clip = clip;

//	printf("Clip: %d %d %d %d\n", clip->x, clip->y, clip->width, clip->height);


	Texture *oldTexture = textureCache.getTexture(clip->width, clip->height);
	int w = 0, h = 0;

	if(oldTexture && !oldTexture->contentsLost() &&
		oldTexture->getWidth() >= clip->width && 
		oldTexture->getHeight() >= clip->height)
	{
		ENTER(Java_scenic_jni_Renderer_beginSurface.1);
//		printf("Reusing texture %d %d\n", context->oldTexture->getWidth(), context->oldTexture->getHeight());
		clip->texture = oldTexture;
		oldTexture = 0;
		clip->textureWidth = clip->texture->getWidth();
		clip->textureHeight = clip->texture->getHeight();
	}
	if(oldTexture)
	{
		w = oldTexture->getWidth();
		h = oldTexture->getHeight();
		oldTexture->release();
		oldTexture = 0;
	}
	if(!clip->texture)
	{
		ENTER(Java_scenic_jni_Renderer_beginSurface.2);
		w = max(clip->width, w + 100);
		h = max(clip->height, h + 100);

		// Some graphics cards (GeForce 2 - 4) do not support small render targets
		if(w < 16)
			w = 16;
		if(h < 16)
			h = 16;

		printf("Create temporary surface (width=%d, height=%d)\n", w, h);
		clip->texture = new Texture(w, h, FMT_A8R8G8B8, USAGE_RENDER_TARGET);
//		clip->texture = new Texture(w, h, FMT_ABGR_16F, USAGE_RENDER_TARGET);
		if(!clip->texture)
		{
			printf("Device::createTexture failed\n");
			
			return 0;
		}
		clip->textureWidth = w;
		clip->textureHeight = h;
	}
	{
	ENTER(Java_scenic_jni_Renderer_beginSurface.3);
	clip->surface = clip->texture->getSurface(0);
	context->setRenderTarget(clip->surface);
	RGBAFloat color;
	
	color.red = color.green = color.blue = color.alpha = 0.0;
	
	context->device->clear(color, NULL);
	}

	return 1;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_drawSurface
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_drawSurface);

	Context *context = getContext(contextId);
	int diffuse = context->color.getRGBA32();
	ClipArea *surface;
	VertexStore vs(VSF_TEX1, context, 0);

	context->setPrimitives(0);

	surface = context->clip;
	context->clip = context->clip->parent;
	context->setRenderTarget(context->clip->surface);
	surface->surface->release();
	surface->surface = NULL;

	for(int y = 0; y < 2; y++)
	{
		for(int x = 0; x < 2; x++)
		{
			double lx = surface->x + x * surface->width;
			double ly = surface->y + y * surface->height;
			double tx1 = (float)x / surface->textureWidth * surface->width;
			double ty1 = (float)y / surface->textureHeight * surface->height;

			vs.addVertex(lx, ly, diffuse, tx1, ty1);
		}
	}

	vs.addTriangle(0, 1, 2);
	vs.addTriangle(3, 2, 1);

	context->device->setTexture(0, surface->texture);
	context->device->setSamplerStates(0, FILTER_POINT, ADDRESS_CLAMP);
	context->device->setTextureStageModulation(0, surface->getModulation());
	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE_MINUS_SRC_ALPHA);
	if(surface->linearColorSpace && !context->clip->linearColorSpace)
		context->device->setState(STATE_SRGBWRITEENABLE, BOOLEAN_TRUE);

	vs.draw();

	context->device->setTexture(0, NULL);
	context->device->setState(STATE_SRGBWRITEENABLE, BOOLEAN_FALSE);

	textureCache.addTexture(surface->texture);
	delete surface;
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_drawSurfaceAndClip
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_drawSurfaceAndClip);

	Context *context = getContext(contextId);
	int diffuse = context->color.getRGBA32();
	ClipArea *surface;
	ClipArea *clip;
	VertexStore vs(VSF_TEX2, context, 0);

	{
	ENTER(Java_scenic_jni_Renderer_drawSurfaceAndClip.1);
	context->setPrimitives(NULL);
	}
	{
	ENTER(Java_scenic_jni_Renderer_drawSurfaceAndClip.2);
	clip = context->clip;
	context->clip = context->clip->parent;
	clip->surface->release();
	clip->surface = NULL;

	surface = context->clip;
	context->clip = context->clip->parent;
	context->setRenderTarget(context->clip->surface);
	surface->surface->release();
	surface->surface = NULL;
	}
	{
	ENTER(Java_scenic_jni_Renderer_drawSurfaceAndClip.3);
	for(int y = 0; y < 2; y++)
	{
		for(int x = 0; x < 2; x++)
		{
			double lx = surface->x + x * surface->width;
			double ly = surface->y + y * surface->height;
			double tx1 = (float)x / surface->textureWidth * surface->width;
			double ty1 = (float)y / surface->textureHeight * surface->height;
			double tx2 = (float)x / clip->textureWidth * clip->width;
			double ty2 = (float)y / clip->textureHeight * clip->height;

			vs.addVertex(lx, ly, diffuse, tx1, ty1, tx2, ty2);
		}
	}

	vs.addTriangle(0, 1, 2);
	vs.addTriangle(3, 2, 1);

	context->device->setTexture(0, surface->texture);
	context->device->setSamplerStates(0, FILTER_POINT, ADDRESS_CLAMP);
	context->device->setTextureStageModulation(0, surface->getModulation());

	context->device->setTexture(1, clip->texture);
	context->device->setSamplerStates(1, FILTER_POINT, ADDRESS_CLAMP);
	context->device->setTextureStageModulation(1, clip->getModulation());

	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE_MINUS_SRC_ALPHA);

	vs.draw();

	context->device->setTexture(0, NULL);
	context->device->setTexture(1, NULL);
	}
	{
	ENTER(Java_scenic_jni_Renderer_drawSurfaceAndClip.4);
	textureCache.addTexture(clip->texture);
	delete clip;
	textureCache.addTexture(surface->texture);
	delete surface;
	}

	
}

JNIEXPORT void JNICALL Java_scenic_jni_Renderer_discardSurface
  (JNIEnv *, jobject, jint contextId)
{
	ENTER(Java_scenic_jni_Renderer_discardSurface);

	Context *context = getContext(contextId);
	ClipArea *clip;

	context->setPrimitives(0);

	clip = context->clip;
	context->clip = context->clip->parent;
	context->setRenderTarget(context->clip->surface);
	delete clip;

	
}

